package org.kolonitsky.alexey.net
{
    import flash.display.Bitmap;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.Sprite;
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.TimerEvent;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.system.SecurityDomain;
    import flash.utils.Timer;
    
    import org.kolonitsky.alexey.events.MultiLoaderEvent;
    import org.kolonitsky.alexey.events.MusicTrackEvent;
    import org.kolonitsky.alexey.sound.MusicTrack;
    
    
    /**
     *  Dispathed on complete loading all resources.
     *
     *  @eventType flash.events.Event.COMPLETE
     */ 
    [Event(name="complete", type="flash.events.Event")]
	
    /**
     *  Dispatched on complete loading a resource.
     *
     *  @eventType org.kolonitsky.alexey.events.MultiLoaderEvent
     */
    [Event(name="partLoaded", type="org.kolonitsky.alexey.events.MultiLoaderEvent")]
    
    /**
     *  Dispatched on change loading progress.
     *
     *  @eventType flash.events.ProgressEvent.PROGRESS
     */
    [Event(name="progress", type="flash.events.ProgressEvent")]
    
    
    /**
     *  MultiLoader load  more then one resource at one time. 
     *
     *  <p>for load images add it to multi loader queue by add methods.</p>
     */ 
    public class MultiLoader extends EventDispatcher
    {
        
        /**
         *  @constructor
	 *  
	 *  @param loadingLimit count of loading threads works together. Default value is -1
	 *  queue not limeted.
         */ 
        public function MultiLoader(loadingLimit:int = -1)
        {
            super();
	    _loadingLimit = loadingLimit;
            updateTimer = new Timer(100);
            updateTimer.addEventListener(TimerEvent.TIMER, updateTimer_timerHandler);
        }
        
        
        //----------------------------------------------------------------------
        //
        // Methods
        //
        //----------------------------------------------------------------------
        
        /**
         *  Add image resource to laoding queue.
         * 
         *  @param id resource ID. By id accessed to loaded resource.
	 * 
	 *  @param url is web address of loaded resource. 
         */ 
        public function addImage(id:String, url:String):void
        {
            var ldr:Loader = new Loader();
            ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, part_completeHandler);
            ldr.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, part_progressHandler);
            ldr.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, part_ioErrorHandler);
            queue[id] = ldr;
            urls[id] = url;
            _totalBytes[id] = Infinity;
            _loadedBytes[id] = 0;
        }
        
        /**
         *  Add sound resource to loading queue.
	 *
	 *  @param id is identifier which used for get loaded resurce. This 
	 *  identifier used as label.
	 * 
	 *  @param url is web address of loaded resource. 
         */ 
        public function addMusic(id:String, url:String):void
        {
            var track:MusicTrack = new MusicTrack();
            track.addEventListener(MusicTrackEvent.LOADING, musicPart_loadingHandler);
            track.addEventListener(IOErrorEvent.IO_ERROR, part_ioErrorHandler);
            track.addEventListener(ErrorEvent.ERROR, part_errorHandler);
            queue[id] = track
            urls[id] = url;
            _totalBytes[id] = Infinity;
            _loadedBytes[id] = 0;
        }
        
        /**
         *  Remove from resource from loading queue by Id.
	 *  @param id of resource for removal 
         */ 
        public function remove(id:String):void
        {
            queue[id] = null
        }
        
        /**
         *  Start all loading.
         */ 
        public function start():void
        {
            updateTimer.start();
	    updateLoadingParts();
        }
        
        /**
         *  Stop all loadings
         */ 
        public function stop():void
        {
            updateTimer.stop();
            for (var id:String in queue)
                queue[id].close();
        }
        
        /**
         *  Get loaded resource as sprite
         */ 
        public function getSprite(id:String):Sprite
        {
	    if (queue && id in queue)
	        queue[id].content as Sprite;
            return null;
        }
        
        /**
         *  Get loaded resource as Bitmap
         */ 
        public function getBitmap(id:String):Loader
        {
            if (queue && id in queue)
                return queue[id] as Loader;
            return null
        }
        
        /**
         *  Get loaded resource as MusicTrack.
         */ 
        public function getMusicTrack(id:String):MusicTrack
        {
	    if (queue && id in queue)
	        queue[id] as MusicTrack;
            return null;
        }

	/**
	 *  Get hash of parts woth loading status.
	 * 
	 *  @return hash of all parts with loading status. 
	 */
        public function getPartsProgress():Object
        {
            var result:Object = {};
            for (var id:String in queue)
                result[id] = uint(100 * _loadedBytes[id] / _totalBytes[id]);
            return result;
        }
        
        
        //----------------------------------------------------------------------
        //
        // Properties
        //
        //----------------------------------------------------------------------
        
        
        //------------------------------
        // count
        //------------------------------
        
        /**
         *  Loading parts count
         */ 
        public function get count():uint
        {
            var result:uint = 0;
            for (var id:String in queue)
                result++
            return result;
        }
        
        
        //------------------------------
        // progress
        //------------------------------
        
        /**
         *  Common progress
         */ 
        public function get progress():Number
        {
            return loadedBytes / totalBytes;
        }
        
        
        //------------------------------
        // loadedBytes
        //------------------------------
        
        private var _loadedBytes:Object = {};
        
	/**
	 *  Loaded bytes in all parts
	 */
        public function get loadedBytes():Number
        {
            var result:Number = 0.0;
            for (var id:String in _loadedBytes)
                result += _loadedBytes[id];
            return result;
        }
        
        
        //------------------------------
        // totalBytes
        //------------------------------
        
        private var _totalBytes:Object = {};
		
	/**
	 *  Total bytes in all parts
	 */
        public function get totalBytes():Number
        {
            var result:Number = 0.0;
            for (var id:String in _totalBytes)
                result += _totalBytes[id];
            return result;
        }
        
        
        //----------------------------------------------------------------------
        //
        // Private logic
        //
        //----------------------------------------------------------------------
		
	private var _loadingLimit:int = -1;
        
	/**
	 *  Is queue of loaded parts
	 */
        private var queue:Object /* of Loader */ = {};
		
        private var urls:Object /* of String*/ = {};
		
	private var loadStatus:Object /* of Boolean */ = {};
        
        private var updateTimer:Timer;
        
        /**
         *  If isLoading flag is true all  
         */ 
        private var isLoading:Boolean = false;
		
	/**
	 *  Start loading of new parts if <code>loadingLimit</code> allow
	 */
	 private function updateLoadingParts():void
	 {
	     var loadingParts:int = 0;
	     
	     var id:String = "";
	     for (id in queue)
	         if (_loadedBytes[id] > 0 && _loadedBytes[id] < _totalBytes[id])
	             loadingParts++;
	     
	     var maxPart:Number = _loadingLimit == -1 ? Infinity : _loadingLimit;
	     
	     if (loadingParts > 0 && maxPart == Infinity)
	       return;
	     
	     for (id in queue)
	     {
		 if (loadingParts >= maxPart)
		     break;
		 
		 if (id in loadStatus)
		 {
		     continue;
		 }
		 else
		 {
		     var request:URLRequest = new URLRequest(urls[id]);
		     
		     var context:LoaderContext = new LoaderContext(true, ApplicationDomain.currentDomain, SecurityDomain.currentDomain);
		     
		     queue[id].load(request);
		     loadStatus[id] = true;
		     loadingParts++;
		 }
	     }
	 }
        
        /**
         *  Get id of part by LoaderInfo object
         *  @return ID string
         */ 
        private function partId(part:*):String
        {
            for (var id:String in queue)
                if (queue[id] is Loader && queue[id].contentLoaderInfo == part)
                    return id;
                else if (queue[id] is MusicTrack && queue[id] == part)
                    return id;
            return null;
        }
		
	private function sendCompleteEvent(partId:String=""):void
	{
	    dispatchEvent(new MultiLoaderEvent(MultiLoaderEvent.PART_LOADED, true, false, partId));
	    
	    updateLoadingParts();
	    
	    if (progress == 1.0)
	    {
		updateTimer.stop();
		dispatchEvent(new Event(Event.COMPLETE));
	    }
	}
	
        
        //----------------------------------------------------------------------
        // Event handlers
        //----------------------------------------------------------------------
        
        private function part_progressHandler(event:ProgressEvent):void
        {
            var id:String = partId(event.target as LoaderInfo)
            _totalBytes[id] = event.bytesTotal;
            _loadedBytes[id] = event.bytesLoaded;
        }
        
        private function part_completeHandler(event:Event):void
        {
            var id:String = partId(event.target as LoaderInfo);
            _loadedBytes[id] = _totalBytes[id];
			sendCompleteEvent(id);
        }
        
        private function musicPart_loadingHandler(event:MusicTrackEvent):void
        {
            var track:MusicTrack = event.currentTarget as MusicTrack;
            var id:String = partId(track)
            _totalBytes[id] = track.length;
            _loadedBytes[id] = track.loadingTime;
        }
        
        private function musicPart_laodHandler(event:MusicTrackEvent):void
        {
            var track:MusicTrack = event.currentTarget as MusicTrack;
            var id:String = partId(track);
            _loadedBytes[id] = _totalBytes[id];
	    sendCompleteEvent(id);
        }
        
        private function part_ioErrorHandler(event:IOErrorEvent):void
        {
            trace("WAR: MultiLoader. On part load " + event.text);
            var loaderInfo:LoaderInfo = event.target as LoaderInfo;
            var id:String = partId(loaderInfo);
            
            loaderInfo.removeEventListener(Event.COMPLETE, part_completeHandler);
            loaderInfo.removeEventListener(ProgressEvent.PROGRESS, part_progressHandler);
            loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, part_ioErrorHandler);
            
            delete queue[id];
            delete urls[id];
            delete _loadedBytes[id];
            delete _totalBytes[id];
        }
        
        private function part_errorHandler(event:ErrorEvent):void
        {
            trace("ERR: MultiLoader. On part load " + event.text);
        }
        
        /**
         *  Update timer
         */ 
        private function updateTimer_timerHandler(event:TimerEvent):void
        {
            dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, true, false, loadedBytes, totalBytes));
        }

    }
}